#include "ani_helpers_build_scripts.h"

#include <ani.h>
#include <cstdlib>
#include <ctime>
#include <fstream>
#include <iostream>
#include <span>
#include <string.h>
#include <unordered_map>
#include <vector>

static void AniMobileLog([[maybe_unused]] FILE *stream, int level, const char *component, const char *msg) {

  std::cout << "[lv: " << level << " " << component << "]: " << msg << std::endl;
}

class PandaOptPrepare {
public:
  PandaOptPrepare() = default;
  PandaOptPrepare(const PandaOptPrepare &) = delete;
  PandaOptPrepare(PandaOptPrepare &&) = delete;
  PandaOptPrepare &operator=(const PandaOptPrepare &) = delete;
  PandaOptPrepare &&operator=(PandaOptPrepare &&) = delete;

  void addOption(const char *opt, void *val) {
    const auto &[it, res] = m_rawOptions.emplace(opt, val);
    if (!res) {
      std::cout << "option already exist: " << opt << std::endl;
      abort();
    }
  }

  ani_options *getPandaOpt() {
    m_holderPandaVMOptions.clear();
    for (const auto &[opt, val] : m_rawOptions) {
      m_holderPandaVMOptions.push_back({opt.data(), val});
    }

    m_holderOptions = {m_holderPandaVMOptions.size(), m_holderPandaVMOptions.data()};
    return &m_holderOptions;
  }

private:
  std::unordered_map<std::string, void *> m_rawOptions;
  std::vector<ani_option> m_holderPandaVMOptions;
  ani_options m_holderOptions;
};

class LauncherOptions {
public:
  LauncherOptions(int argc, const char *argv[]) {
    constexpr const char *vmKeyWord = "--PandaOpt";

    std::span<const char *> spanArg(argv, argc);
    for (const char *argChar : spanArg) {
      std::string_view arg = argChar;
      auto posDel = arg.find('=');
      std::string_view optName = arg.substr(0, posDel);
      const char *optVal = arg.substr(posDel + 1).data();
      if (optName == vmKeyWord) {
        pandaOptPrepare_.addOption(optVal, nullptr);
        continue;
      }

      LauncherOpts_.insert({optName, optVal});
    }
  }

  void addPandaOption(const char *opt, void *val) { pandaOptPrepare_.addOption(opt, val); }

  ani_options *getPandaOpt() { return pandaOptPrepare_.getPandaOpt(); }

  const char *getEntryClass() const { return LauncherOpts_.at("--entry-class"); }

  const char *getEntryStaticMethod() const { return LauncherOpts_.at("--entry-static-method"); }

  bool hasEntryStringARG() const { return LauncherOpts_.count("--entry-string-arg"); }

  const char *getEntryStringARG() const { return LauncherOpts_.at("--entry-string-arg"); }

  bool hasPreloadClassList() const { return LauncherOpts_.count("--preload-class-list"); }

  const char *getPreloadClassList() const { return LauncherOpts_.at("--preload-class-list"); }

private:
  std::unordered_map<std::string_view, const char *> LauncherOpts_;
  PandaOptPrepare pandaOptPrepare_;
};

// static void addProfileOtions(){
//[[maybe_unused]] auto getDateTime = []() {
// auto t = std::time(nullptr);
// auto tm = *std::localtime(&t);

// std::ostringstream oss;
// oss << std::put_time(&tm, "%Y%m%d_%H%M%S");
// return oss.str();
//};

// std::string samplingProfilerOutput;
// samplingProfilerOutput += "--ext:--sampling-profiler-output-file=";
// samplingProfilerOutput += std::getenv("BUILD_DIR");
// samplingProfilerOutput += "/outfile.aspt";

// auto& pandaOptPrepare = getPandaOptPrepare();
////pandaOptPrepare.addOption("--ext:--sampling-profiler-enable", nullptr);
// pandaOptPrepare.addOption("--ext:--sampling-profiler-interval=1", nullptr);
// pandaOptPrepare.addOption(samplingProfilerOutput.c_str(), nullptr);
// pandaOptPrepare.addOption("--ext:--log-level=debug", nullptr);
// pandaOptPrepare.addOption("--ext:--log-components=profiler", nullptr);
// pandaOptPrepare.addOption("--ext:--workers-type=threadpool", nullptr);
//}

static ani_vm *CreateVM(LauncherOptions &opts) {
  ani_vm *vm{};
  CHECK_ANI_FATAL(ANI_CreateVM(opts.getPandaOpt(), ANI_VERSION_1, &vm));
  return vm;
}

static void runETSNoArg(ani_vm *vm, const char *entryClass, const char *entryStaticMethod) {
  ani_env *env;
  CHECK_ANI_FATAL(vm->GetEnv(ANI_VERSION_1, &env));

  ani_class cls;
  CHECK_ANI_FATAL(env->FindClass(entryClass, &cls));

  ani_static_method start;
  CHECK_ANI_FATAL(env->Class_FindStaticMethod(cls, entryStaticMethod, ":", &start));
  CHECK_ANI_FATAL(env->Class_CallStaticMethod_Void(cls, start));
}

static void runETSStringArg(ani_vm *vm, const char *entryClass, const char *entryStaticMethod, const char *arg) {
  ani_env *env;
  CHECK_ANI_FATAL(vm->GetEnv(ANI_VERSION_1, &env));

  ani_class cls;
  CHECK_ANI_FATAL(env->FindClass(entryClass, &cls));

  ani_static_method start;
  CHECK_ANI_FATAL(env->Class_FindStaticMethod(cls, entryStaticMethod, "C{std.core.String}:", &start));

  ani_string argString{};
  CHECK_ANI_FATAL(env->String_NewUTF8(arg, strlen(arg), &argString));

  CHECK_ANI_FATAL(env->Class_CallStaticMethod_Void(cls, start, argString));
}

static void LoadPreloadClassList(const char *filePath, ani_env *env) {
  std::ifstream file(filePath);
  if (!file)
    INTEROP_FATAL("Failed to open file: %s", filePath);

  std::string className;
  ani_class loadedCls;
  while (std::getline(file, className)) {
    // std::cout << "Loading class: " << className << std::endl;
    CHECK_ANI_FATAL(env->FindClass(className.c_str(), &loadedCls));
  }
}

int main(int argc, const char *argv[]) {
  LauncherOptions opts(argc, argv);
  opts.addPandaOption("--logger", reinterpret_cast<void *>(AniMobileLog));

  ani_vm *vm = CreateVM(opts);

  if (opts.hasPreloadClassList()) {
    ani_env *env = nullptr;
    vm->GetEnv(ANI_VERSION_1, &env);
    LoadPreloadClassList(opts.getPreloadClassList(), env);
  }

  if (opts.hasEntryStringARG()) {
    runETSStringArg(vm, opts.getEntryClass(), opts.getEntryStaticMethod(), opts.getEntryStringARG());
  } else {
    runETSNoArg(vm, opts.getEntryClass(), opts.getEntryStaticMethod());
  }

  std::cout << "Well, at least you see last log" << std::endl;
  return 0;
}
